/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2008-2014 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_DRAWABLE
#define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_DRAWABLE 1

#include "Common"
#include "TileRenderModel"
#include "GeometryPool"
#include <osg/Geometry>
#include <osg/Image>
#include <osg/Matrixf>
#include <osgEarth/TileKey>
#include <osgEarth/Map>

using namespace osgEarth;

namespace osgEarth { namespace REX
{
    class EngineContext;

    struct ModifyBoundingBoxCallback : public osg::Referenced
    {
        ModifyBoundingBoxCallback(EngineContext* context);
        void operator()(const TileKey& key, osg::BoundingBox& bbox);
        EngineContext* _context;
    };

    /**
     * TileDrawable is an osg::Drawable that represents an individual terrain tile
     * for the purposes of scene graph operations (like intersections, bounds
     * computation, statistics, etc.)
     * 
     * NOTE: TileDrawable does not actually render anything!
     * The TerrainRenderData object does all the rendering of tiles.
     *
     * Instead, it exposes various osg::Drawable Functors for traversing
     * the terrain's geometry. It also hold a pointer to the tile's elevation
     * raster so it can properly reflect the elevation data in the texture.
     */
    class TileDrawable : public osg::Drawable //, public PatchLayer::GeometryArrayProvider
    {
    public:
        // underlying geometry, possibly shared between this tile and other.
        osg::ref_ptr<SharedGeometry> _geom;

        // tile dimensions
        int _tileSize;

        const TileKey _key;

        osg::ref_ptr<const osg::Image> _elevationRaster;
        osg::Matrixf                   _elevationScaleBias;

        // cached 3D mesh of the terrain tile (derived from the elevation raster)
        std::vector<osg::Vec3> _mesh;
        osg::BoundingBox _bboxOffsets;
        ModifyBoundingBoxCallback* _bboxCB;
        mutable float _bboxRadius;

    public:
        
        // construct a new TileDrawable that fronts an osg::Geometry
        TileDrawable(
            const TileKey& key,
            SharedGeometry* geometry,
            int            tileSize);

    public:

        // Sets the elevation raster for this tile
        void setElevationRaster(const osg::Image* image, const osg::Matrixf& scaleBias);

        const osg::Image* getElevationRaster() const {
            return _elevationRaster.get();
        }

        const osg::Matrixf& getElevationMatrix() const {
            return _elevationScaleBias;
        }

        // Set the render model so we can properly calculate bounding boxes
        void setModifyBBoxCallback(ModifyBoundingBoxCallback* bboxCB) { _bboxCB = bboxCB; }

        float getRadius() const { return _bboxRadius; }

        float getWidth() const { return getBoundingBox().xMax() - getBoundingBox().xMin(); }

    public: // osg::Drawable overrides

        // These methods defer functors (like stats collection) to the underlying
        // (possibly shared) geometry instance.
        bool supports(const osg::Drawable::AttributeFunctor& f) const { return true; }
        void accept(osg::Drawable::AttributeFunctor& f) { if ( _geom.valid() ) _geom->accept(f); }

        bool supports(const osg::Drawable::ConstAttributeFunctor& f) const { return true; }
        void accept(osg::Drawable::ConstAttributeFunctor& f) const { if ( _geom.valid() ) _geom->accept(f); }

        bool supports(const osg::PrimitiveFunctor& f) const { return true; }
        void accept(osg::PrimitiveFunctor& f) const;
        
        /** indexed functor is NOT supported since we need to apply elevation dynamically */
        bool supports(const osg::PrimitiveIndexFunctor& f) const { return false; }

        osg::BoundingSphere computeBound() const;
        osg::BoundingBox computeBoundingBox() const;

    public: // GeometryArrayProvider interface

        const osg::Array* getVertexArray() const { return _geom->getVertexArray(); }
        const osg::Array* getNormalArray() const { return _geom->getNormalArray(); }
        const osg::Array* getTexCoordArray() const { return _geom->getTexCoordArray(); }
        const osg::BoundingBox& getBoundingBox() const { return osg::Drawable::getBoundingBox(); }

    public:
        META_Object(osgEarth, TileDrawable);
        TileDrawable() : osg::Drawable(), _tileSize(0), _bboxCB(NULL), _bboxRadius(0.0) {}
        TileDrawable(const TileDrawable& rhs, const osg::CopyOp& cop) 
         : osg::Drawable(rhs, cop)
         , _tileSize(rhs._tileSize)
         , _mesh(rhs._mesh)
         , _bboxCB(rhs._bboxCB)
         , _bboxRadius(rhs._bboxRadius)
        {}

        virtual ~TileDrawable();
    };

} } // namespace osgEarth::REX

#endif // OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_RexGEOMETRY

